// snippet.hide
import _NIOFileSystem
import NIOCore

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
func main() async throws
{
  // snippet.show

  // NIOFileSystem provides access to the local file system via the FileSystem
  // type which is available as a global shared instance.
  let fileSystem = FileSystem.shared

  // Files can be inspected by using 'info':
  if let info = try await fileSystem.info(forFileAt: "/Users/hal9000/demise-of-dave.txt") {
    print("demise-of-dave.txt has type '\(info.type)'")
  } else {
    print("demise-of-dave.txt doesn't exist")
  }

  // Let's find out what's in that file.
  do {
    // Reading a whole file requires a limit. If the file is larger than the limit
    // then an error is thrown. This avoids accidentally consuming too much memory
    // if the file is larger than expected.
    let plan = try await ByteBuffer(
      contentsOf: "/Users/hal9000/demise-of-dave.txt",
      maximumSizeAllowed: .mebibytes(1)
    )
    print("Plan for Dave's demise:", String(decoding: plan.readableBytesView, as: UTF8.self))
  } catch let error as FileSystemError where error.code == .notFound {
    // All errors thrown by the module have type FileSystemError (or
    // Swift.CancellationError). It looks like the file doesn't exist. Let's
    // create it now.
    //
    // The code above for reading the file is shorthand for opening the file in
    // read-only mode and then reading its contents. The FileSystemProtocol
    // has a few different 'withFileHandle' methods for opening a file in different
    // modes. Let's open a file for writing, creating it at the same time.
    try await fileSystem.withFileHandle(
      forWritingAt: "/Users/hal9000/demise-of-dave.txt",
      options: .newFile(replaceExisting: false)
    ) { file in
      let plan = ByteBuffer(string: "TODO...")
      try await file.write(contentsOf: plan.readableBytesView, toAbsoluteOffset: 0)
    }
  }

  // Directories can be opened like regular files but they cannot be read from or
  // written to. However, their contents can be listed:
  let path: FilePath? = try await fileSystem.withDirectoryHandle(atPath: "/Users/hal9000/Music") { directory in
    for try await entry in directory.listContents() {
      if entry.name.extension == "mp3", entry.name.stem.contains("daisy") {
        // Found it!
        return entry.path
      }
    }
    // No luck.
    return nil
  }

  if let path = path {
    print("Found file at '\(path)'")
  }

  // The file system can also be used to perform the following operations on files
  // and directories:
  // - copy,
  // - remove,
  // - rename, and
  // - replace.
  //
  // Here's an example of copying a directory:
  try await fileSystem.copyItem(at: "/Users/hal9000/Music", to: "/Volumes/Tardis/Music")

  // Symbolic links can also be created (and read with 'destinationOfSymbolicLink(at:)').
  try await fileSystem.createSymbolicLink(at: "/Users/hal9000/Backup", withDestination: "/Volumes/Tardis")

  // Opening a symbolic link opens its destination so in most cases there's no
  // need to read the destination of a symbolic link:
  try await fileSystem.withDirectoryHandle(atPath: "/Users/hal9000/Backup") { directory in
    // Beyond listing the contents of a directory, the directory handle provides a
    // number of other functions, many of which are also available on regular file
    // handles.
    //
    // This includes getting information about a file, such as its permissions, last access time,
    // and last modification time:
    let info = try await directory.info()
    print("The directory has permissions '\(info.permissions)'")

    // Where supported, the extended attributes of a file can also be accessed, read, and modified:
    for attribute in try await directory.attributeNames() {
      let value = try await directory.valueForAttribute(attribute)
      print("Extended attribute '\(attribute)' has value '\(value)'")
    }

    // Once this closure returns the file system will close the directory handle freeing
    // any resources required to access it such as file descriptors. Handles can also be opened
    // with the 'openFile' and 'openDirectory' APIs but that places the onus you to close the
    // handle at an appropriate time to avoid leaking resources.
  }
  // snippet.end
}
